/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package preka;

import java.awt.Dimension;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import prefuse.Constants;
import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.RepaintAction;
import prefuse.action.animate.LocationAnimator;
import prefuse.action.assignment.ColorAction;
import prefuse.action.assignment.DataColorAction;
import prefuse.action.layout.Layout;
import prefuse.action.layout.RandomLayout;
import prefuse.action.layout.graph.ForceDirectedLayout;
import prefuse.activity.Activity;
import prefuse.controls.DragControl;
import prefuse.controls.PanControl;
import prefuse.controls.ToolTipControl;
import prefuse.controls.ZoomControl;
import prefuse.controls.ZoomToFitControl;
import prefuse.data.Edge;
import prefuse.data.Graph;
import prefuse.data.Node;
import prefuse.data.Tuple;
import prefuse.data.event.TupleSetListener;
import prefuse.data.io.GraphMLReader;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.LabelRenderer;
import prefuse.data.io.DataIOException;
import prefuse.data.tuple.TupleSet;
import prefuse.util.ColorLib;
import prefuse.util.force.ForceItem;
import prefuse.util.force.ForceSimulator;
import prefuse.util.force.SpringForce;
import prefuse.visual.EdgeItem;
import prefuse.visual.VisualItem;
import preka.clustering.NoClustering;
import preka.prefuse.action.Heuristic;
import preka.prefuse.action.IVCFarthestFirst;
import preka.prefuse.action.RandomMove;
import preka.prefuse.action.animate.IVCLocationAnimator;
import preka.prefuse.action.layout.graph.IVCForceDirectedLayout;
import preka.prefuse.controls.IVCDragControl;
import preka.prefuse.data.io.ArffReader;
import preka.prefuse.visual.VisualInstance;
import preka.prefuse.visual.VisualInstances;
import weka.clusterers.InstancePair;
import weka.clusterers.MPCKMeans;
import weka.clusterers.metriclearners.WEuclideanLearner;
import weka.core.Attribute;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.KDTree;
import weka.core.SelectedTag;
import weka.core.metrics.WeightedEuclidean;


/**
 *
 * @author vhbarros
 */
public class IVC {

    // Estados do processo IVC
    public static final int EXIT_OK = 0;
    public static final int EXIT_ERRORS = -1;
    public static final int NOT_RUNNING = 1;
    public static final int RUNNING = 2;

    // Desenhos suportados pelo IVC
    public static final int NO_LAYOUT = 0;
    public static final int FORCE_DIRECTED = 1;
    
    // Algoritmos de geração de ligações relacionais
    public static final int NO_RELATIONAL_DATA = 0;
    public static final int NEAREST_NEIGHBOUR = 1;

    // Algoritmos de agrupamento suportados pelo IVC
    public static final int NO_CLUSTERING = 0;
    public static final int MPCKMEANS = 1;

    // Heurísticas suportadas pelo IVC
    public static final int NO_HEURISTIC = 0;
    public static final int FARTHEST_FIRST = 1;
    public static final int RANDOM = 2;

    // Grupos de items Visualização
    public static String GRAPH_GROUP    = "graph";          // Grafo incluído na visualização
    public static String NODES_GROUP    = "graph.nodes";    // Nós do grafo
    public static String EDGES_GROUP    = "graph.edges";    // Ligações do grafo

    // Atributos para geração do grafo
    public static String INSTANCE_ATTRIBUTE     = "instance";
    public static String CLASS_ATTRIBUTE        = "class";
    public static String EDGE_TYPE_ATTRIBUTE    = "type";

    // Propriedades das ligações
    public static String EDGE_CLUSTERING = "0";
    public static String EDGE_RELATIONAL = "1";
    public static String EDGE_BOTH       = "2";

    // Acções visualização
    public static String ANIMATOR_ACTION     = "animator";
    public static String HEURISTIC_ACTION    = "heuristic";
    public static String HEURISTIC_ANIMATOR  = "animatorh";

    // Tempo de cada passo por omissão (em milisegundos)
    // TODO Melhoria: O tempo de cada acção não deve ser fixo, mas antes dependente de uma medida de "estabilização" do layout (variação média da força de 1 nó?)
    public static int DEFAULT_STEP_TIME = 15000;

    // Variáveis da classe
    private int m_status;
    private Graph m_graph;
    private Visualization m_vis;
    private IVCDragControl m_dragControl;
    private ArrayList m_relData             = null;
    private VisualInstances m_visualInstances;
    private double [] m_oldX, m_oldY;
    private int m_isLayoutAlive             = 0;

    // Clustering
    private MPCKMeans m_mpckmeans;                  // TODO Melhoria: classe devia se a super do MPCKMeans ou Object
    private NoClustering m_noClustering;
    private boolean m_processConstraints;
    private boolean m_hasCenters;
    private ArrayList m_constraints;
    private Instances m_data                = null; // Instâncias do conjunto de dados (carregadas do .arff)
    private Instances m_clusterData         = null; // Instâncias do conjunto de dados, sem informação da classe
    private int[] m_clusterVisualMapping;

    // Layout
    private Layout m_layout;
    private long m_layoutDuration;
    private boolean m_hasLayoutRun          = false;

    

    // ********* Parametrizações do IVC
    private int m_clusteringMethod;       // Algoritmo de agrupamento de dados com restrições utilizado
    private int m_layoutMethod;           // Algoritmo de desenho utilizado
    private int m_relationalDataMethod;   // Utiliza a informação relacional do conjunto de dados?
    private int m_heuristic;              // Algoritmo de simulação do utilizador utilizado

    // Distâncias entre as instâncias para criação de restrições
    private double m_mustLinkDistance       = 113;//227;    // Máxima para restrições de lig. obrigatória
    private double m_cannotLinkDistance     = 533;    // Mínima para restrições de lig. proibida

    // Variáveis de execução
    private int m_iteration;                        // Iteracção



    // ********* Construtores IVC
    public IVC() {

        // Variável de controlo do estado do processo IVC
        m_status              = IVC.NOT_RUNNING;

        // Por omissão o método é criado com o MPCK-Médias e o Spring Embedded
        m_clusteringMethod    = IVC.MPCKMEANS;
        m_layoutMethod        = IVC.FORCE_DIRECTED;

        // Por omissão são gerados dados relacionais através da heurística vizinho mais próximo
        m_relationalDataMethod = IVC.NEAREST_NEIGHBOUR;

        // Por omissão não é utilizada qualquer heurística de simulação de utilizadores
        m_heuristic           = IVC.NO_HEURISTIC;
    }

    public IVC(int clusteringMethod, int layoutMethod, int relationalDataMethod, int heuristic) {
        m_status                = IVC.NOT_RUNNING;
        m_clusteringMethod      = clusteringMethod;
        m_layoutMethod          = layoutMethod;
        m_relationalDataMethod  = relationalDataMethod;
        m_heuristic             = heuristic;
    }



    // ******** Getters
    public Visualization getVisualization() {
        return m_vis;
    }

    public Layout getLayout() {
        return m_layout;
    }

    public int getLayoutMethod() {
        return m_layoutMethod;
    }

    public Display getDisplay() {
        return m_vis.getDisplay(0);
    }

    public double getMustLinkDistance() {
        return m_mustLinkDistance;
    }

    public IVCDragControl getIVCDragControl(){
        return m_dragControl;
    }

    public VisualInstances getVisualInstances(){
        return m_visualInstances;
    }


    public int[] getClusterAssignments() {

        int[] clusterAssignments = null;

        switch (m_clusteringMethod) {

            // Sem clustering - Neste caso é simulado um clustering por proximidade do centro
            case IVC.NO_CLUSTERING:

                if (m_noClustering != null) {
                    clusterAssignments = m_noClustering.getClusterAssignments();
                    performVisualClusterMapping(clusterAssignments);
                    //printVisualClusterMapping();
                }

                return clusterAssignments;

            // MPCK-Médias
            case IVC.MPCKMEANS:
                // continuar aqui!!
                if (m_mpckmeans != null) {
                    clusterAssignments = m_mpckmeans.getClusterAssignments();
                    performVisualClusterMapping(clusterAssignments);
                    //printVisualClusterMapping();
                }

                return clusterAssignments;

            // TODO Novos algoritmos ADR: Getter dos cluster assignments deve implementada aqui!

            default:
                // TODO Melhoria: tratamento de excepções
                return clusterAssignments;
        }

    }

    // TODO Melhoria: optimizar o método e o processo! (Só deve ser feito até serem conhecidos todos os mapeamentos)
    public void performVisualClusterMapping(int[] clusterAssignments) {

        //ArrayList visualClusters = new ArrayList();

        if(m_clusterVisualMapping == null)
            m_clusterVisualMapping = new int[m_data.numClasses()];

        // Inicialização do mapeamento dos centros a 0
        for (int i=0; i < m_data.numClasses(); i++)
            m_clusterVisualMapping[i] = (i+1)*-1;

    }

    public int getClosestCenterInstance(double x, double y) {

        int centerInstance = 0;
        double dist, distance = Double.MAX_VALUE;

        ArrayList centers = m_visualInstances.getCenterVisualInstances();
        Iterator it = centers.iterator();
        while (it.hasNext()) {
            VisualInstance vinst = (VisualInstance) it.next();

            dist = distance(x, y, vinst.getX(), vinst.getY());

            //System.out.println(" - inst:"+ vinst.getNumInstance()+" ("+x+","+y+") dist:"+dist+" ("+vinst.getX()+","+vinst.getY()+")");

            if (dist < distance) {
                distance = dist;
                centerInstance = vinst.getNumInstance();
            }
        }

        return centerInstance;
    }

    public void printVisualClusterMapping() {

        System.out.println("Visual Cluster Mapping (Clustering/Visualization):");
        for (int i=0; i < m_data.numClasses(); i++)
            System.out.println(" - Clustering: "+i+" Visualization:"+m_clusterVisualMapping[i]);
        
    }


    public int getNumClusters() throws Exception {

        switch (m_clusteringMethod) {

            // Sem clustering - Neste caso é simulado um clustering por proximidade do centro
            case IVC.NO_CLUSTERING:

                if (m_noClustering != null)
                    return m_data.numClasses();
                else
                    return 0;

            // MPCK-Médias
            case IVC.MPCKMEANS:
                if (m_mpckmeans != null)
                    return m_mpckmeans.getNumClusters();
                else
                    return 0;

            // TODO Novos algoritmos ADR: Getter do número de grupos deve implementada aqui!

            default:
                // TODO Melhoria: tratamento de excepções
                return 0;
        }

    }

    public int getNumClasses() {
        return m_data.numClasses();
    }

    public String getClassName(int index) {
        return m_data.classAttribute().value(index);

    }

    public int getNumMovedInstances() {
        return m_visualInstances.getNumMovedInstances();
    }

    public int getIteration() {
        return m_iteration;
    }

    public int getStatus() {
        return m_status;
    }

    public ArrayList getConstraints() {
        return m_constraints;
    }

    public String [] getAttributes() {

        String [] tooltip = new String[m_data.numAttributes()+1];   // Nº da instância e respectivos atributos
        tooltip[0] = IVC.INSTANCE_ATTRIBUTE;
        for (int i=1; i <= m_data.numAttributes(); i++)
            tooltip[i] = m_data.attribute(i-1).name();

        return tooltip;
    }

    public boolean isAlive() {
        int comp = 0;

        Iterator items = m_vis.items(IVC.NODES_GROUP);
        while (items.hasNext()){
            VisualItem vit = (VisualItem) items.next();

            if (vit.getInt(IVC.INSTANCE_ATTRIBUTE) == -1) {
                if (m_oldX[0] == vit.getX() && m_oldY[0] == vit.getY())
                    comp++;
                else {
                    m_oldX[0] = vit.getX();
                    m_oldY[0] = vit.getY();
                    m_isLayoutAlive = 0;
                }
            }
            
            if (vit.getInt(IVC.INSTANCE_ATTRIBUTE) == -2) {
                if (comp == 1 && m_oldX[1] == vit.getX() && m_oldY[1] == vit.getY())
                    comp++;
                else {
                    m_oldX[1] = vit.getX();
                    m_oldY[1] = vit.getY();
                    m_isLayoutAlive = 0;
                }
            }

            if (vit.getInt(IVC.INSTANCE_ATTRIBUTE) == -3) {
                if (comp == 2 && m_oldX[2] == vit.getX() && m_oldY[2] == vit.getY())
                    comp++;
                else {
                    m_oldX[2] = vit.getX();
                    m_oldY[2] = vit.getY();
                    m_isLayoutAlive = 0;
                }
            }

            if (vit.getInt(IVC.INSTANCE_ATTRIBUTE) == -4) {
                if (comp == 3 && m_oldX[3] == vit.getX() && m_oldY[3] == vit.getY())
                    comp++;
                else {
                    m_oldX[3] = vit.getX();
                    m_oldY[3] = vit.getY();
                    m_isLayoutAlive = 0;
                }
            }
        }

        if (comp == m_data.numClasses())
            m_isLayoutAlive++;

        // Aguarda 30 segundos até dizer que o layout está parado
        if(m_isLayoutAlive == 30)
            return false;

        return true;
    }

    // Setters
    public void setIteration(int iteration) {
        m_iteration = iteration;
    }

    public void incrementIteration() {
        m_iteration++;
    }


    // Métodos das operações core do IVC
    public void loadGraph(String fileSrc) throws Exception {

        // *************** 1. Leitura do grafo inicial
        m_graph = null;

        try {
            ArffReader r = new ArffReader();
            r.readArff(fileSrc);
            m_data = r.getData();

            m_oldX = new double[m_data.numClasses()];
            m_oldY = new double[m_data.numClasses()];

            //String fileRel = fileSrc + ".relations";
            //String fileTrg = fileSrc + ".xml";

            if (m_relationalDataMethod != IVC.NO_RELATIONAL_DATA && m_relData == null)
                generateRelationalData();
                //m_relData = r.readRelations(fileRel);

            /*if (m_data == null) { System.out.println("null");
                r.writeGraphML(fileTrg, null, 0, null, m_relData); }
            else { System.out.println("not null");
                 r.writeGraphML(fileTrg, this.buildClusterer(), this.getNumClusters(), m_clusterVisualMapping, m_relData);}*/

            //m_graph = new GraphMLReader().readGraph("/socialnet.xml");
            //m_graph = new GraphMLReader().readGraph("/home/vhbarros/Dropbox/Tese/Software/preka/data/teste.xml");
            //m_graph = new GraphMLReader().readGraph(fileTrg);
            m_graph = r.getGraph(m_relData);
            

        } catch ( DataIOException e ) {
            e.printStackTrace();
            System.err.println("Error loading graph. Exiting...");
            System.exit(1);
        }
    }

    public void reloadGraph() {

        // Supende as acções em execução para proceder às alterações ao grafo
        stopVisualization();

        if (m_clusteringMethod != IVC.NO_CLUSTERING) {
            // Remove todos os arcos de clustering -- os arcos TÊM de ser removidos do último para o 1º (bug prefuse)
            int oldEdgeNum = m_graph.getEdgeCount();
            for (int i=oldEdgeNum-1; i>=0; i--) {
                Edge e = m_graph.getEdge(i);
                if (e.getString(IVC.EDGE_TYPE_ATTRIBUTE).equals(IVC.EDGE_CLUSTERING)) {
                    //System.out.println(" --- Edge:"+e.toString());
                    m_graph.removeEdge(e);
                }
            }

            // Se não foram eliminados arcos de clustering, então devem ser criados os centroides para as novas ligações de clustering
            if (oldEdgeNum == m_graph.getEdgeCount())
                createCenterNodes();
                /*System.out.println("não eliminou");
            else
                System.out.println("eliminou");*/

            m_visualInstances.updatePositions(m_vis, IVC.NODES_GROUP);
            updateCenterNodes();

            int [] clusters = getClusterAssignments();
            for (int i=0 ; i < m_data.numInstances(); i++) {
                int cluster = (clusters[i]+1)*-1; // converte para o código da instância visual
                Node s = getNodeByInstanceAttr(cluster);
                Node t = m_graph.getNode(i);

                //System.out.println("Instância:"+i+" Cluster:"+cluster+" ("+clusters[i]+")");
                Edge e = m_graph.addEdge(s, t);
                e.set(IVC.EDGE_TYPE_ATTRIBUTE, IVC.EDGE_CLUSTERING);


            }
        } else { // (m_clusteringMethod == IVC.NO_CLUSTERING)
            if (!m_hasCenters) {
                createCenterNodes();
                m_hasCenters = true;
            }

            m_visualInstances.updatePositions(m_vis, IVC.NODES_GROUP);
            updateCenterNodes();
        }

        // Retoma a execução das acções da visualização
        runVisualization();

    }

    public void createCenterNodes () {

        for (int i=0 ; i < m_data.numClasses(); i++) {
            Node n = m_graph.addNode();
            n.set(IVC.INSTANCE_ATTRIBUTE, (i+1)*-1);  // Para distinguir os centros, estes são identificados como negativos

            for (int j=0; j < m_data.numAttributes(); j++) {
                Attribute atr = m_data.attribute(j);
                n.set(atr.name(), "-1");
            }

            //VisualItem vi = m_vis.getVisualItem(IVC.NODES_GROUP, n);
            //vi.setX(0);
            //vi.setY(0);
            //vi.setFixed(true);
            //vi.setInteractive(false);

        }
    }

    public void updateCenterNodes() {

        //TODO: Workaround!! Melhorar criação centros iniciais!!
        if (m_clusteringMethod == IVC.NO_CLUSTERING) {
            if(m_noClustering.setInitialCenters())
                try {
                    runClusterer(null);
                } catch (Exception ex) {
                    Logger.getLogger(IVC.class.getName()).log(Level.SEVERE, null, ex);
                }
        }

        Iterator items = m_vis.items(IVC.NODES_GROUP);
        while (items.hasNext()){
            VisualItem vit = (VisualItem) items.next();

            if (vit.getInt(IVC.INSTANCE_ATTRIBUTE) < 0)  // Processa apenas os centros
            {
                int visualCluster = vit.getInt(IVC.INSTANCE_ATTRIBUTE);
                //System.out.println("Processando o centro: "+visualCluster);

                ArrayList movedInstances = new ArrayList();
                int numInstancesAffected = 0;
                double x = 0, y = 0;

                // Adiciona as instâncias movidas, para processamento
                movedInstances = m_visualInstances.getMovedVisualInstances();

                VisualInstance vinst;
                Iterator it = movedInstances.iterator();
                while (it.hasNext()) {
                    vinst = (VisualInstance) it.next();

                    if (vinst.getVisualCluster() == visualCluster) {
                        //System.out.println(" --- eu ("+vinst.getNumInstance()+") sou do cluster "+vinst.getCluster());
                        x += vinst.getX();
                        y += vinst.getY();
                        numInstancesAffected++;
                    }

                }

                if (numInstancesAffected != 0) {

                    // Calcula o ponto médio entre as instâncias
                    x = x/numInstancesAffected;
                    y = y/numInstancesAffected;

                    // Actualiza a posição do centro
                    //vit.setX(x);
                    //vit.setY(y);
                    vit.setStartX(vit.getX());
                    vit.setStartY(vit.getY());
                    vit.setEndX(x);
                    vit.setEndY(y);
                    vit.setFixed(true);
                    vit.setInteractive(false);
                    m_vis.run(IVC.ANIMATOR_ACTION);
                }
                else {
                    x = 0;
                    y = 0;

                    //vit.setX(x);
                    //vit.setY(y);
                    vit.setStartX(vit.getX());
                    vit.setStartY(vit.getY());
                    vit.setEndX(x);
                    vit.setEndY(y);
                    vit.setFixed(true);
                    vit.setInteractive(false);
                }
            }
        }

    }

    public Node getNodeByInstanceAttr (int instance) {

        Iterator nodes = m_graph.nodes();
        while (nodes.hasNext()) {
            Node n = (Node) nodes.next();
            if (n.getInt(IVC.INSTANCE_ATTRIBUTE) == instance)
                return n;
        }

        return null;
    }

    // TODO Melhoria: Implementar outros métodos além do kNN + melhorar performance!
    public void generateRelationalData() throws Exception {

        if (m_relData != null)
            return;

        int numOK=0, numNOK=0;
        m_relData = new ArrayList();
        double [][] distMatrix = new double[m_data.numInstances()][m_data.numAttributes()-1]; // Todos os atributos, excepto a classe

        // Coloca os atributos das instâncias numa matriz
        // TODO: Melhoria: Implementar para atributos não numéricos
        for (int i=0; i < m_data.numInstances(); i++)
            for (int j=0; j < m_data.numAttributes()-1; j++)  // Todos os atributos, excepto a classe
            {
                distMatrix[i][j] = m_data.instance(i).value(j);
                //System.out.println("Instância: "+i+" Atributo: "+j+" Valor: "+distMatrix[i][j]);
            }

        // Obtem o vizinho mais próximo para cada instância
        for (int i=0; i < m_data.numInstances(); i++)
        {
            double minDist = Double.MAX_VALUE;
            int [] relMatrix = new int [2];

            for (int j=0; j < m_data.numInstances(); j++)
                if(j != i) {
                    double atrDist = 0;
                    double instDist = 0;
                    
                    for (int k=0; k < m_data.numAttributes()-1; k++)  // Cálculo da distância euclediana N-dimensional
                        atrDist += Math.pow((distMatrix[i][k] - distMatrix[j][k]),2);

                    instDist = Math.sqrt(atrDist);
                    if (minDist > instDist) {
                        minDist = instDist;
                        if (i > j) {
                            relMatrix[0] = j;
                            relMatrix[1] = i;
                        } else {
                            relMatrix[0] = i;
                            relMatrix[1] = j;
                        }
                    }
                }

            // m_relData.contains(relMatrix) não funciona com objectos int[]!! :(
            Iterator rels = m_relData.iterator();
            boolean found = false;
            while (rels.hasNext() && !found) {
                int [] rel = (int []) rels.next();
                if (rel[0] == relMatrix[0] && rel[1] == relMatrix[1])
                    found = true;
            }

            if(!found)
                m_relData.add(relMatrix);

            
            if (found) {
                String s = "grupos diferentes";
                if(m_data.instance(relMatrix[0]).classValue() ==  m_data.instance(relMatrix[1]).classValue()) {
                    s = "mesmo grupo";
                    numOK++;
                }
                else
                    numNOK++;
                System.out.println("O vizinho mais próximo de "+relMatrix[0]+" é "+relMatrix[1]+ "(distância "+minDist+") "+s);
            }
        }
        System.out.println("relações ok:"+numOK+" nok:"+numNOK);


    }

    private void createHeuristicAction()
    {
        ActionList animatorh;
        ActionList heuristic;
        int animatorDuration, stepTime;
        if (m_layoutMethod == IVC.NO_LAYOUT){
            animatorDuration = 100;
            stepTime = 300;
        } else {
            animatorDuration = 1000;
            stepTime = 3000;
        }


        // *************** Criação do objecto de tratamento do layout, consoante o algoritmo definido
        switch (m_heuristic) {

            // Sem heurística de simulação de utilizador
            case IVC.NO_HEURISTIC:
                // Neste caso não é necessária a criação do objecto do layout
                break;

            // Heurística de selecção do nó mais afastado do grupo correcto
            case IVC.FARTHEST_FIRST:
                animatorh = new ActionList(animatorDuration);
                animatorh.add(new IVCLocationAnimator(IVC.NODES_GROUP, true, m_visualInstances));

                heuristic = new ActionList(Activity.INFINITY);
                heuristic.setStepTime(stepTime);
                heuristic.add(new IVCFarthestFirst(this ,IVC.GRAPH_GROUP));
                //animator.alwaysRunAfter(heuristic);

                m_vis.putAction(IVC.HEURISTIC_ANIMATOR, animatorh);
                m_vis.putAction(IVC.HEURISTIC_ACTION, heuristic);

                break;

            // Heurística de selecção aleatória de nós
            case IVC.RANDOM:
                animatorh = new ActionList(animatorDuration);
                animatorh.add(new IVCLocationAnimator(IVC.NODES_GROUP, true, m_visualInstances));

                heuristic = new ActionList(Activity.INFINITY);
                heuristic.setStepTime(stepTime);
                heuristic.add(new RandomMove(this ,IVC.GRAPH_GROUP));
                //animator.alwaysRunAfter(heuristic);

                m_vis.putAction(IVC.HEURISTIC_ANIMATOR, animatorh);
                m_vis.putAction(IVC.HEURISTIC_ACTION, heuristic);
                break;

            default:
                // TODO Melhoria: tratamento de excepções
                break;
        }
    }


    // TODO Melhoria: tornar atribuição de cores dinâmica ()
    private int[] getNodesPalette() {

        int[] palette = new int[m_data.numClasses()+1];
        int color = 0;

        for (int i=0; i <= m_data.numClasses(); i++) {

            switch (i) {
                case 0:
                  color = ColorLib.rgb(255,180,180); // Rosa
                  //color = ColorLib.rgb(190,190,255); // Azul
                  break;
                case 1:
                  //color = ColorLib.rgb(255,180,180); // Rosa
                  color = ColorLib.rgb(190,190,255); // Azul
                  break;
                case 2:
                  color = ColorLib.rgb(35,142,35); // 35;142;35 Forest Green
                  break;
                case 3:
                  color = ColorLib.rgb(205,179,139); // 205;179;139 NavajoWhite3
                  break;
                case 4:
                  color = ColorLib.rgb(255,193,37); // 255;193;37  goldenrod1
                  break;
                default:
                    break;
            }

            palette[i] = color;
        }

        return palette;
    }

    public void createVisualization() {

        m_status = IVC.RUNNING;

        // *************** 2. Criação da visualização
        m_vis = new Visualization();
        m_vis.add(IVC.GRAPH_GROUP, m_graph);                // O grafo será identificado como "graph" na visualização
                                                            // Os nós e as ligações ficam automaticamente registados como
                                                            // "graph.nodes" e "graph.edges" respectivamente
        m_vis.setInteractive(IVC.EDGES_GROUP, null, false); // A interactividade com as ligações fica inibida

        // Carrega as VisualInstances para permitir a reposição de posições entre
        // os diferentes passos
        m_visualInstances = new VisualInstances(m_vis,                // Visualização criada
                                                IVC.NODES_GROUP,      // Grupo dos nós (instâncias)
                                                m_data.numClasses()); // Número de classes (para definição dos centros iniciais)

        //m_visualInstances.printVisualInstances();


        // *************** 3. Criação dos renderizadores para definir o aspecto dos nós e das ligações

        // 3.1. O LabelRenderer serve para inserir texto dentro do símbolo visual
        LabelRenderer tr = new LabelRenderer("instance"); //new LabelRenderer("class");
        tr.setRoundedCorner(8, 8);

        // 3.2. O DefaultRendererFactory é criado com o renderizador por omissão para as ligações
        //      (linhas normais) e o LabelRenderer para os nós
        m_vis.setRendererFactory(new DefaultRendererFactory(tr));


        /*TupleSet focusGroup = m_vis.getGroup(Visualization.FOCUS_ITEMS);
        focusGroup.addTupleSetListener(new TupleSetListener() {
            public void tupleSetChanged(TupleSet ts, Tuple[] add, Tuple[] rem)
            {
            	System.out.println("hey!");

                for ( int i=0; i<rem.length; ++i )
                    ((VisualItem)rem[i]).setFixed(false);
                for ( int i=0; i<add.length; ++i ) {
                    ((VisualItem)add[i]).setFixed(false);
                    ((VisualItem)add[i]).setFixed(true);
                }
                if ( ts.getTupleCount() == 0 ) {
                    ts.addTuple(rem[0]);
                    ((VisualItem)rem[0]).setFixed(false);
                }
                m_vis.run("draw");
            }
        });*/


        // *************** 4. Complementar os símbolos visuais com as propriedades visual, neste passo cor!

        // Vector de cores distintas a aplicar às instâncias, correspondende ao número de classes
        // 0 representa opacidade e 255 transparência total
        int[] palette = getNodesPalette();
        
        int[] edgesPalette = new int[] {
            // IndianRed4 139;58;58
            //ColorLib.rgb(255,0,0),      // ligações de agrupamento não são visíveis, por omissão
            //ColorLib.rgb(0,0,0)             // ligações relacionais
            ColorLib.rgb(255,255,255),
            ColorLib.rgb(255,255,255)
        };



        // 4.1. Definição da cor de fundo de cada objecto (distinta consante a classe real a que pertence)
        DataColorAction fill = new DataColorAction(IVC.NODES_GROUP,         // Aplica-se aos nós do grafo
                                                   "class",                 // Campo identificador da classe
                                                   Constants.NOMINAL,       // Indica que é um identificador de conjunto
                                                   VisualItem.FILLCOLOR,    // Aplica-se à cor de fundo do nó
                                                   palette);                // Cores distintas a aplicar aos objectos de cada classe

        // 4.2. Definição da cor do texto
        ColorAction text = new ColorAction(IVC.NODES_GROUP,         // Aplica-se aos nós do grafo
                                           VisualItem.TEXTCOLOR,    // Aplica-se ao texto do objecto
                                           ColorLib.gray(0));       // Preto

        // 4.3. Definição da cor das ligações
        DataColorAction edges = new DataColorAction(IVC.EDGES_GROUP,         // Aplica-se às ligações
                                                    "type",                  // Campo identificador do tipo de ligação (relacional ou clustering)
                                                    Constants.NOMINAL,       // Indica que é um identificador de conjunto
                                                    VisualItem.STROKECOLOR,  // Aplica-se à cor das linhas
                                                    edgesPalette);           // Cores distintas a aplicar aos objectos de cada classe


        //ColorAction edges = new ColorAction("graph.edges",          // Aplica-se às ligações
        //                                    VisualItem.STROKECOLOR, // Aplica-se à cor das linhas
        //                                    ColorLib.gray(200));    // Cinza claro

        edges.add(VisualItem.HIGHLIGHT, ColorLib.rgb(255, 0, 0));



        // 4.4. Criação de uma lista de acções para ser executada (e aplicar as cores) no loop da visualização
        ActionList color = new ActionList();
        color.add(fill);
        color.add(text);
        color.add(edges);

        // *************** 5. Aplicar um desenho à visualização
        createLayout();
        ActionList layout = new ActionList(m_layoutDuration);//(15000); //(Activity.INFINITY);  // A acção será executada até ser mandada parar
        layout.add(m_layout);           // Aplicação do desenho de forças direccionadas spring embedded ao grafo
        //layout.add(new FDLTeste(IVC.GRAPH_GROUP, true));
        ActionList repaint = new ActionList(Activity.INFINITY);
        repaint.add(new RepaintAction());                        // Para redesenhar a cada ciclo

        //ActionList repaint = new ActionList(Activity.DEFAULT_STEP_TIME);
        //repaint.add(new RepaintAction());

        // *************** 6. Associar acções à visualização
        m_vis.putAction("color", color);
        m_vis.putAction("layout", layout);
        m_vis.putAction("repaint", repaint);
        createHeuristicAction();

        ActionList animator = new ActionList(2000);
        //animator.add(new IVCLocationAnimator(IVC.NODES_GROUP, true, m_visualInstances));
        animator.add(new LocationAnimator(IVC.NODES_GROUP));
        m_vis.putAction(IVC.ANIMATOR_ACTION, animator);

        // *************** 7. Construção da área de apresentação da visualização
        /* Display display = new Display(m_vis);           // Associa a visualização à area de apresentação
        display.setSize(600, 600);                      // Tamanho da área de visualização
        display.pan(300, 300);
        display.addControlListener(new DragControl());  // Controlo para permitir arrastar as instâncias
        display.addControlListener(new PanControl());   // Controlo para mover a visualização com o botão esquerdo do rato
        display.addControlListener(new ZoomControl());  // Controlo para fazer zoom o botão direito do rato


        //
        JFrame frame = new JFrame("Interactive Visual Clustering");
        // ensure application exits when window is closed
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(display);
        frame.pack();           // layout components in window
        frame.setVisible(true); // show the window

        m_vis.run("color");  // assign the colors
        m_vis.run("layout");*/ // start up the animated layout

    }

    public void createDisplay(int width, int height) {

        // *************** 7. Construção da área de apresentação da visualização
        Display display = new Display(m_vis);           // Associa a visualização à area de apresentação
        display.setSize(width, height);                 // Tamanho da área de visualização
        display.pan(width/2, height/2);                 // Centra a apresentação do grafo
        Point center = new Point(width/2, height/2);
        display.zoom(center, 0.75);

        m_dragControl = new IVCDragControl(m_visualInstances);
        display.addControlListener(m_dragControl);          // Controlo para permitir arrastar as instâncias (e actualizar as instâncias movidas)
        display.addControlListener(new PanControl());       // Controlo para mover a visualização com o botão esquerdo do rato
        display.addControlListener(new ZoomControl());      // Controlo para fazer zoom o botão direito do rato
        display.addControlListener(new ZoomToFitControl()); // Controlo para fazer zoom automático com duplo clique
        display.addControlListener(new ToolTipControl(getAttributes()));    // Controlo para apresentar o tooltip com os atributos
        

    }

    public void stopVisualization() {
        m_vis.cancel("color");  // Suspende a lista de acções que aplica as cores
        m_vis.cancel("repaint");
        if (m_layoutMethod != IVC.NO_LAYOUT || !m_hasLayoutRun)
            m_vis.cancel("layout"); // Suspende a lista de acções que aplica o desenho
        if (m_heuristic != IVC.NO_HEURISTIC)
            m_vis.cancel("heuristic");
    }

    public void runVisualization() {
        m_vis.run("color");  // Inicia a lista de acções que aplica as cores
        m_vis.run("repaint");
        if (m_layoutMethod != IVC.NO_LAYOUT || !m_hasLayoutRun) {
            m_vis.run("layout"); // Inicia a lista de acções que aplica o desenho
            m_hasLayoutRun = true;
        }
        if (m_heuristic != IVC.NO_HEURISTIC)
            m_vis.run("heuristic");
        //m_vis.alwaysRunAfter("heuristic", IVC.ANIMATOR_ACTION);

        //m_visualInstances.applyPositions(m_vis, IVC.NODES_GROUP, m_constraints);
        //m_vis.repaint();
    }

    /*public void reloadVisualization() {

        // Guarda as últimas posições dos objectos
        m_visualInstances.updatePositions(m_vis, IVC.NODES_GROUP);

        // continuar!!!
        // Adiciona o novo grafo gerado
        m_vis.removeGroup(IVC.GRAPH_GROUP);
        m_vis.add(IVC.GRAPH_GROUP, m_graph);

        // Aplica as últimas posições dos objectos no novo grafo, fixando os movidos anteriormente
        m_visualInstances.applyPositions(m_vis, IVC.NODES_GROUP, m_constraints);


        \*VisualItem vi = null;
        Iterator t = m_vis.items(IVC.NODES_GROUP);
        while (t.hasNext()) {
            vi = (VisualItem) t.next();
            if (vi.getSourceTuple().getInt(IVC.INSTANCE_ATTRIBUTE) == 121) {
                vi.setX(200);
                vi.setY(200);
                vi.setFixed(true);
            }

        }*\

        m_vis.run("color");  // Inicia a lista de acções que aplica as cores
        m_vis.run("layout"); // Inicia a lista de acções que aplica o desenho
        //m_vis.run("repaint");

        //m_vis.repaint();
    }*/

    // *************************************************************************
    // ***************************** Layout ************************************
    // *************************************************************************
    public void createLayout() {

        // *************** Criação do objecto de tratamento do layout, consoante o algoritmo definido
        switch (m_layoutMethod) {

            // Sem desenho - Neste caso serão apresentadas as instâncias aleatoriamente na visualização
            case IVC.NO_LAYOUT:
                if (m_layout == null)
                    m_layoutDuration = Activity.DEFAULT_STEP_TIME;
                    m_layout = new RandomLayout(IVC.GRAPH_GROUP);
                break;

            // Force Directed - Spring Embedded
            case IVC.FORCE_DIRECTED:

                if (m_layout == null)
                    m_layoutDuration = Activity.INFINITY;
                    m_layout = new IVCForceDirectedLayout(IVC.GRAPH_GROUP, true);
                break;

            // TODO Novos algoritmos Layout: Inicialização deve implementada aqui!

            default:
                // TODO Melhoria: tratamento de excepções
                break;
        }
    }

    public void registerActionListLayout() {

        switch (m_layoutMethod) {

            // Sem desenho
            case IVC.NO_LAYOUT:
                // Neste caso não é necessária a criação do objecto do layout
                break;

            // Force Directed - Spring Embedded
            case IVC.FORCE_DIRECTED:

                m_vis.removeAction("layout");

                ActionList layout = new ActionList(Activity.INFINITY);  // A acção será executada até ser mandada parar
                layout.add(m_layout);           // Aplicação do desenho de forças direccionadas spring embedded ao grafo
                layout.add(new RepaintAction());                        // Para redesenhar a cada ciclo

                m_vis.putAction("layout", layout);

                break;

            // TODO Novos algoritmos Layout: Registo na visualização deve implementado aqui!

            default:
                // TODO Melhoria: tratamento de excepções
                break;
        }
    }


    // *************************************************************************
    // *************************** Clustering **********************************
    // *************************************************************************
    public void createClusterer() {

        // *************** Criação do objecto de tratamento do clustering, consoante o algoritmo definido
        switch (m_clusteringMethod) {

            // Sem clustering - Neste caso é simulado um clustering por proximidade do centro
            case IVC.NO_CLUSTERING:
                m_processConstraints = false;
                m_hasCenters = false; // TODO: alagar lógica aos outros métodos e uniformizar reloadGraph
                m_noClustering = new NoClustering(m_vis, m_visualInstances, m_data);
                
                break;

            // MPCK-Médias
            case IVC.MPCKMEANS:
                m_processConstraints = true;
                m_mpckmeans = new MPCKMeans();
                m_mpckmeans.setUseMultipleMetrics(false);       // Utiliza apenas uma métrica (euclediana pesada)

                WeightedEuclidean metric = new WeightedEuclidean();
                WEuclideanLearner metricLearner = new WEuclideanLearner();
                m_mpckmeans.setMetric(metric);
                m_mpckmeans.setMetricLearner(metricLearner);

                m_mpckmeans.setVerbose(false);
                m_mpckmeans.setRegularize(false);
                m_mpckmeans.setTrainable(new SelectedTag(MPCKMeans.TRAINING_INTERNAL, MPCKMeans.TAGS_TRAINING));
                m_mpckmeans.setSeedable(true);

                // Criar um novo conjunto para efectuar o clustering
                m_clusterData = new Instances(m_data);
                m_clusterData.deleteClassAttribute();         // Para o clustering não vai o atributo da classe

                break;

            // TODO Novos algoritmos ADR: Inicialização deve implementada aqui!
                
            default:
                // TODO Melhoria: tratamento de excepções
                break;
        }

        m_constraints = new ArrayList(); // TODO Melhoria: Reposicionar para construtor
    }

    public void runClusterer(ArrayList constraints) throws Exception {
        
        // *************** Execução do clustering com aplicação das restrições, consoante o algoritmo definido
        switch (m_clusteringMethod) {

            // Sem clustering - Neste caso é simulado um clustering por proximidade do centro
            case IVC.NO_CLUSTERING:
                m_noClustering.buildClusterer();
                m_visualInstances.updateVisualClusters(m_data.numInstances(), m_noClustering.getClusterAssignments());
                break;

            // MPCK-Médias
            case IVC.MPCKMEANS:
                
                m_mpckmeans.buildClusterer(constraints,
                                           m_clusterData,
                                           m_data,
                                           m_data.numClasses(),
                                           m_data.numInstances());
                m_visualInstances.updateClusters(m_data.numInstances(), m_mpckmeans.getClusterAssignments());
                //m_mpckmeans.printClusterAssignments();

                break;

            // TODO Novos algoritmos ADR: Execução deve implementada aqui!
                
            default:
                // TODO Melhoria: tratamento de excepções
                break;
        }

    }

    public ArrayList processConstraints() {
        if (!m_processConstraints)
            return null;

        // As restrições devem ser limpas a cada iteracção
        m_constraints.clear();

        ArrayList movedVisualItems = m_visualInstances.getMovedVisualItems(m_vis, IVC.NODES_GROUP);
        double x0, y0, x1, y1;
        int inst0, inst1;

        // Loop de instâncias movidas
        Iterator mi = movedVisualItems.iterator();
        //System.out.println("hey! while!");
        while (mi.hasNext()) {
            //System.out.println("hey! 1");
            VisualItem vmi = (VisualItem) mi.next();
            //System.out.println("hey! 2");
            x0 = vmi.getX();
            //System.out.println("hey! x:"+x0);
            y0 = vmi.getY();
            //System.out.println("hey! y:"+y0);
            inst0 = vmi.getSourceTuple().getInt(IVC.INSTANCE_ATTRIBUTE);     // Obtém o nº da instância movida

            //System.out.println("Processando a instância movida "+inst0+/*" ("+vi.getSourceTuple().getInt(IVC.INSTANCE_ATTRIBUTE)+*/")...");

            
            // **** Processamento de restrições de ligação obrigatória
            // Devem ser verificados todas as instâncias que estejam a uma distância (euclediana) na
            // visualização inferior ao parâmetro para criação de ligações obrigatórias
            Iterator ml;
            if (m_layoutMethod != IVC.NO_LAYOUT)
                ml = m_vis.items(IVC.NODES_GROUP);
            else
                ml = movedVisualItems.iterator(); // Caso nao haja layout, só devem ser consideradas as instâncias movidas

            while (ml.hasNext()) {
                VisualItem viml = (VisualItem) ml.next();
                x1 = viml.getX();
                y1 = viml.getY();
                inst1 = viml.getSourceTuple().getInt(IVC.INSTANCE_ATTRIBUTE); // Nº da instância para gerar a restrição

                //System.out.println(" - instância"+viml.getSourceTuple().getInt(IVC.INSTANCE_ATTRIBUTE)+"...");

                if (inst1 > -1 &&                                   // Se: a instância não for um centro de grupo
                    inst1 != inst0 &&                               //     e não for ela própria
                    distance(x0, y0, x1, y1) < m_mustLinkDistance)  //     e estiver dentro da distância parametrizada
                {

                    /*System.out.println(" --- restrição lig. obrigatória! i1:"+inst0+
                                                                       " i2:"+inst1+
                                                                       " dist:"+distance(x0, y0, x1, y1));*/
                    InstancePair pair;
                    if (inst0 < inst1) {
                        pair = new InstancePair(inst0,
                                                inst1,
                                                InstancePair.MUST_LINK); // Para integrar com o Weka! :)    
                    }
                    else // troca! O Weka exige que as restrições estejam assim organizadas
                    {
                        pair = new InstancePair(inst1,
                                                inst0,
                                                InstancePair.MUST_LINK);
                    }


                    if (!m_constraints.contains(pair))
                    {
                        pair.toString();
                        m_constraints.add(pair);
                        //System.out.println("Adicionou!");
                    }

                    // TODO retirar: temporário!
                    // viml.setFillColor(ColorLib.rgb(255, 0, 0));
                    // m_vis.run("color");
                    // m_vis.repaint();
                }


            }


            // **** Processamento de restrições de ligação proibida
            // Devem ser verificadas as instâncias movidas que estejam a uma distância (euclediana) na
            // visualização superior ao parâmetro para criação de ligações proibidas
            Iterator cl = movedVisualItems.iterator();
            while (cl.hasNext()) {
                VisualItem vicl = (VisualItem) cl.next();
                x1 = vicl.getX();
                y1 = vicl.getY();
                inst1 = vicl.getSourceTuple().getInt(IVC.INSTANCE_ATTRIBUTE); // Nº da instância para gerar a restrição

                //System.out.println(" - instância"+vicl.getSourceTuple().getInt(IVC.INSTANCE_ATTRIBUTE)+"...");

                if (inst1 < inst0 &&                                // Se: a instância a comparar for superior à anterior
                    inst1 > -1 &&                                   //     a instância não for um centro de grupo
                    inst1 != inst0 &&                               //     e não for ela própria
                    distance(x0, y0, x1, y1) > m_cannotLinkDistance)//     e estiver fora da distância parametrizada
                {

                    /*System.out.println(" --- restrição lig. proibida! i1:"+inst0+
                                                                    " i2:"+inst1+
                                                                    " dist:"+distance(x0, y0, x1, y1));*/
                    
                    InstancePair pair;
                    if (inst0 < inst1) {
                        pair = new InstancePair(inst0,
                                                inst1,
                                                InstancePair.CANNOT_LINK); // Para integrar com o Weka! :)
                    }
                    else // troca! O Weka exige que as restrições estejam assim organizadas
                    {
                        pair = new InstancePair(inst1,
                                                inst0,
                                                InstancePair.CANNOT_LINK);
                    }


                    if (!m_constraints.contains(pair))
                    {
                        m_constraints.add(pair);
                        //System.out.println("Adicionou!");
                    }

                    // TODO retirar: temporário!
                    // vicl.setFillColor(ColorLib.rgb(255, 0, 0));
                    // m_vis.run("color");
                    // m_vis.repaint();
                }

            }
        }

        return m_constraints;

    }

    public static double distance(double x1, double y1, double x2, double y2) {
        return Math.sqrt(Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2));
    }

    // TODO uniformizar randIndex!!
    public double randIndex() throws Exception {

        int[] clusterAssignments;
        Instances ins;
        if(m_clusteringMethod == IVC.NO_CLUSTERING) {
            clusterAssignments = m_noClustering.getClusterAssignments();
            ins = m_data;
        }
        else {
            clusterAssignments = m_mpckmeans.getClusterAssignments();
            ins = m_mpckmeans.getTotalTrainWithLabels();
        }

        Instance inst1, inst2;
        String label1, label2;
        double numCorrect = 0, numInstances = m_data.numInstances();

        for (int i = 0; i < numInstances; i++) {
            for (int j = i + 1; j < numInstances; j++) {
                int cluster_i = clusterAssignments[i];
                int cluster_j = clusterAssignments[j];
                double class_i = (ins.instance(i)).classValue();
                double class_j = (ins.instance(j)).classValue();
                //	      System.out.println(cluster_i + "," + cluster_j + ":" + class_i + "," + class_j);
                if (cluster_i == cluster_j && class_i == class_j
                                || cluster_i != cluster_j && class_i != class_j) {
                        numCorrect++;
                        //		  System.out.println("nCorrect:" + nCorrect);
                }
            }
        }

        double RandIndex = /*100 **/ numCorrect
                        / (numInstances * (numInstances - 1) / 2);
        //System.out.println("NumCorrect:"+numCorrect+" NumInstances:"+numInstances+" RandIndex:"+RandIndex);
        return RandIndex;
    }

    /*public static void main(String[] Args) {

        //IVC ivc = new IVC();
        //System.out.println("res: "+String.valueOf(ivc.distance(50,50,39,39)));
        //System.out.println("res: "+Math.round(102/3) );

        for (int i=0; i<10; i++)
            System.out.println("rand:"+Math.random());
    }*/

}
